package ru.home.documentmanager.service.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBElement;

import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.ContentAccessor;
import org.docx4j.wml.Text;

public class DocUtil {
    public static void replaceText(WordprocessingMLPackage template,
            Map<String, String> map) {
        TextFinder finder = new TextFinder();
        List<Object> texts = finder.getAllElementFromObject(
                template.getMainDocumentPart(), Text.class);
        State state = new State(finder.parents, map);
        Iterator<Object> iter = texts.iterator();
        while (iter.hasNext()) {
            state.next(iter);
        }
    }

    private static class State {

        static final String START = "${";
        static final String END = "}";
        static final String END_START = "^\\}([\\s]*)\\$\\{$";
        static final Pattern endStartPattern = Pattern.compile(END_START);

        Text[] state = new Text[3];
        int index = 0;
        String val;
        Text text;
        boolean leaveSpaces = false;

        Map<Object, ContentAccessor> parents;
        Map<String, String> replaceMap;

        public State(Map<Object, ContentAccessor> parents,
                Map<String, String> replace) {
            this.parents = parents;
            this.replaceMap = replace;
        }

        void add(Text text) {
            state[index] = text;
            index++;
        }

        void next(Iterator<Object> iter) {
            text = (Text) iter.next();
            val = text.getValue().trim();
            System.out.println("[" + val + "]");
            switch (index) {
            case 0:
                if (START.equals(val)) {
                    add(text);
                }
                break;
            case 1:
                add(text);
                break;
            case 2:
                if (END.equals(val)) {
                    add(text);
                    finish(true);
                } else if (val != null && val.matches(END_START)) {
                    System.out.println("\\END_START detected");
                    finish(false);
                    add(text);
                } else {
                    clear();
                }
            }
        }

        void clear() {
            index = 0;
            state[0] = null;
            state[1] = null;
            state[2] = null;
        }

        void finish(boolean deleteEnd) {
            String replacement = replaceMap.get(state[1].getValue());
            replace(replacement);
            if (deleteEnd) {
                if (leaveSpaces) {
                    leaveSpaces = false;
                } else {
                    delete(state[0]);
                }
                delete(state[2]);
            } else {
                delete(state[0]);
                leaveSpaces = true;
                System.out.println("\\finish (false)");
            }
            clear();
        }

        void replace(String replacement) {
            if (leaveSpaces) {
                Matcher m = endStartPattern.matcher(state[0].getValue());
                m.find();
                state[0].setValue(m.group(1));
                state[0].setSpace("preserve");
                System.out.println("\\leaveSpaces, result = ["
                        + state[0].getValue() + "]");
            }
            state[1].setValue(replacement);
        }

        void delete(Text text) {
            Object textElement = text.getParent();
            ContentAccessor parent = parents.get(textElement);
            if (parent != null)
                parent.getContent().remove(textElement);
        }
    }

    private static class TextFinder {

        private Map<Object, ContentAccessor> parents = new HashMap<>();

        private List<Object> getAllElementFromObject(Object obj,
                Class<?> toSearch) {
            List<Object> result = new ArrayList<Object>();
            if (obj instanceof JAXBElement)
                obj = ((JAXBElement<?>) obj).getValue();
            if (obj.getClass().equals(toSearch))
                result.add(obj);
            else if (obj instanceof ContentAccessor) {
                ContentAccessor caObj = (ContentAccessor) obj;
                List<?> children = caObj.getContent();
                for (Object child : children) {
                    parents.put(child, caObj);
                    result.addAll(getAllElementFromObject(child, toSearch));
                }
            }
            return result;
        }
    }

}
